﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Resources;
using System.Drawing;
using System.Windows.Forms;
using System.IO;
using System.Diagnostics;
using System.Threading;
using System.Xml.Linq;

namespace fm2
{
	public class MainForm : Form
	{
		NotifyIcon notifyIcon;
		Icon mainIcon;
		Icon folderIcon;

		SettingsForm settingsForm;

		ToolStripEx actionControl;
		ProgressBarEx pbarControl; 
		TabControl tabControl;
		DataGridViewEx<LogItem> dgvHistory;
		DataGridViewEx<FileItem> dgvFiles;
		System.Windows.Forms.Timer timer;

		List<string> MonitoringPaths = new List<string>();
		bool floatUp = false;
		bool isForeground = false;

		List<FileSystemWatcher> watchers = new List<FileSystemWatcher>();

		Action InstanceIncrementCounter;
		Action InstanceViewChanges;

		Thread thread;
		static Node<DirItem, FileItem> Root;
		static Node<DirItem, FileItem> currentRoot;
		static List<Node<DirItem, FileItem>> dirs = new List<Node<DirItem, FileItem>>();

		public void InitializeComponent()
		{
			settingsForm = new SettingsForm();

			MinimumSize = new Size(600, 400);
			Padding = new Padding(0,2,0,0);

			ResourceManager resources = new ResourceManager("fm2.Resource", System.Reflection.Assembly.GetExecutingAssembly());
			mainIcon = (Icon) resources.GetObject("main");
			folderIcon = (Icon) resources.GetObject("folder");
			Icon = folderIcon;
			Text = "FM";
			KeyPreview = true;

			notifyIcon = new NotifyIcon();
			notifyIcon.Icon = folderIcon;
			notifyIcon.Visible = true;
			notifyIcon.ContextMenuStrip = new ContextMenuStrip();
			notifyIcon.ContextMenuStrip.Items.AddRange(
				new ToolStripItem[] {
					new ToolStripMenuItem("Update", (Image) resources.GetObject("refresh"),  (o,a) => SetCheckChangesTime() ),
					new ToolStripMenuItem("Settings", (Image) resources.GetObject("settings"), (o,a) => SettingsFormShow() ),
					new ToolStripSeparator(),
					new ToolStripMenuItem("Exit", (Image) resources.GetObject("exit"), (o,a) => Application.Exit() )
				});
			notifyIcon.MouseMove += (o,a) => isForeground = SafeNativeMethods.GetForegroundWindow() == Handle; 
			notifyIcon.MouseClick += NotifyIconClick;
			notifyIcon.MouseDoubleClick += NotifyIconClick;

			tabControl = new TabControl();
			tabControl.TabPages.Add("History");
			tabControl.TabPages.Add("Files");
			tabControl.Dock = DockStyle.Fill;
			tabControl.ItemSize = new Size(tabControl.ItemSize.Width, 21);

			actionControl = new ToolStripEx();
			actionControl.Dock = DockStyle.None;
			actionControl.GripStyle = ToolStripGripStyle.Hidden;
			actionControl.Renderer = new ToolStripSystemRendererEx();
			actionControl.Location = new Point(95, 0);
			actionControl.Items.AddRange(
				new ToolStripItem[] {
					new ToolStripButton( "", (Image) resources.GetObject("refresh"), (o,a) => SetCheckChangesTime(), "btnRefresh" ),
					new ToolStripSeparator(),
					new ToolStripButton( "", (Image) resources.GetObject("open"), (o,a) => Open(false), "btnOpen" ),
					new ToolStripButton( "", (Image) resources.GetObject("opendir"), (o,a) => Open(true), "btnOpendir" ),
					new ToolStripSeparator(),
					new ToolStripButton( "", (Image) resources.GetObject("clear"), (o,a) => dgvHistory.ClearData(), "btnClear" ),
					new ToolStripSeparator(),
					new ToolStripButton( "", (Image) resources.GetObject("settings"), (o,a) => SettingsFormShow(), "btnSettings" )
				} );
			actionControl.Items["btnRefresh"].ToolTipText = "Update (F5)";
			actionControl.Items["btnOpen"].ToolTipText = "Open selected files";
			actionControl.Items["btnOpendir"].ToolTipText = "Open selected folders";
			actionControl.Items["btnClear"].ToolTipText = "Clear history";
			actionControl.Items["btnSettings"].ToolTipText = "Settings";

			pbarControl = new ProgressBarEx();

			dgvFiles = new DataGridViewEx<FileItem>();
			dgvFiles.SortedColumn = dgvFiles.Columns[1];
			dgvFiles.SortedColumn.HeaderCell.SortGlyphDirection = SortOrder.Ascending;

			dgvHistory = new DataGridViewEx<LogItem>();
			foreach(DataGridViewColumn c in dgvHistory.Columns) c.SortMode = DataGridViewColumnSortMode.NotSortable; 

			tabControl.TabPages[0].Controls.Add(dgvHistory);
			tabControl.TabPages[1].Controls.Add(dgvFiles);
			Controls.AddRange( new Control[] { actionControl, pbarControl, tabControl } );

			pbarControl.Location = new Point(actionControl.Left + actionControl.Width + 10, actionControl.Top + 1);

			timer = new System.Windows.Forms.Timer();
			timer.Interval = 600;
		}
		public MainForm()
		{
			InitializeComponent();

			InstanceIncrementCounter = IncrementCounter;
			InstanceViewChanges = ViewChanges;

			timer.Tick += TimerTick;
			this.Shown += MainFormShown;
			this.Activated += MainFormActivated;
			this.FormClosing += MainFormClosing;
			this.KeyDown += delegate(object sender, KeyEventArgs e) {
				if (e.KeyCode == Keys.Escape) {
					WindowState = FormWindowState.Minimized;
				} else if (e.KeyCode == Keys.F5) {
					SetCheckChangesTime();
				}
			};
		}
		public void MainFormShown(object sender, EventArgs e)
		{
			SettingsLoad();
			ResizeEnd += (o,a) => SettingsSave();
			dgvFiles.ColumnWidthChanged += (o,a) => SettingsSave(); 
			dgvFiles.CellMouseDoubleClick += DgvCellMouseDoubleClick;
			dgvHistory.ColumnWidthChanged += (o,a) => SettingsSave();
			dgvHistory.CellMouseDoubleClick += DgvCellMouseDoubleClick;

			RunThread();

			WatchersCreate();
		}
		public void MainFormActivated(object sender, EventArgs e)
		{
			if (Icon != folderIcon) {
				Icon = notifyIcon.Icon = folderIcon;
			}
		}
		public void MainFormClosing(object sender, EventArgs e)
		{
			if (thread.IsAlive) {
				thread.Abort();
			}
			SettingsSave();
			notifyIcon.Dispose();
		}
		
		public void NotifyIconClick(object sender, MouseEventArgs e)
		{
			if (e.Button == MouseButtons.Left) {
				if (isForeground) {
					WindowState = FormWindowState.Minimized;
					Visible = false;
				} else {
					Visible = true;
					SafeNativeMethods.ShowWindow(Handle, SafeNativeMethods.SW_RESTORE);
					SafeNativeMethods.SetForegroundWindow(Handle);
				}
			}
		}
		public void DgvCellMouseDoubleClick(object sender, DataGridViewCellMouseEventArgs e)
		{
			if (e.ColumnIndex >= 0 && e.RowIndex >= 0 && e.Button == MouseButtons.Left) {
				Open(false);
			}
		}
		public void WatchersCreate()
		{
			foreach(string path in MonitoringPaths) {
				if (Directory.Exists(path)) {
					FileSystemWatcher watcher = new FileSystemWatcher();
					watcher.Path = path;
					watcher.IncludeSubdirectories = true;
					watcher.Changed += OnChanged;
					watcher.Created += OnChanged;
					watcher.Deleted += OnChanged;
					watcher.Renamed += OnChanged;
					watcher.EnableRaisingEvents = true;
					watchers.Add(watcher);
				}
			}
		}
		public void WatchersClear()
		{
			foreach(var watcher in watchers) {
				watcher.Changed -= OnChanged;
				watcher.Created -= OnChanged;
				watcher.Deleted -= OnChanged;
				watcher.Renamed -= OnChanged;
			}
			watchers.Clear();
		}
		public void SetCheckChangesTime()
		{
			pbarControl.Text = "Check...";
			pbarControl.Value = 100;
			pbarControl.Refresh();
		}
		public void TimerTick(object sender, EventArgs e)
		{
			timer.Stop();
			if (pbarControl.Value < 100) {
				pbarControl.Value++;
				timer.Start();
			} else {
				RunThread();
			}
			pbarControl.Refresh();
		}
		public void Open(bool opendir)
		{
			DataGridView dgv = tabControl.SelectedIndex == 0 ? (DataGridView)dgvHistory : (DataGridView)dgvFiles;
			var paths = (from DataGridViewRow row in dgv.Rows where row.Selected select row.Cells[1].Value.ToString()).ToArray();
			new Thread( () => {
				foreach(string path in paths) {
					if (File.Exists(path)) {
						try {
							if (opendir) {
								Process.Start("explorer.exe", string.Format("/select,\"{0}\"", path));
							} else {
								Process.Start(path);
							}
							Thread.Sleep(200);
						} catch(Exception ex) {
							if (MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OKCancel, MessageBoxIcon.Error) == DialogResult.Cancel) {
								break;
							}
						}
					}
				}
			} ).Start();
		}

		private void OnChanged(object source, FileSystemEventArgs e)
		{
			if (pbarControl.InvokeRequired) {
				pbarControl.Invoke( InstanceIncrementCounter );
			} else {
				IncrementCounter();
			}
		}
		void IncrementCounter()
		{
			if (floatUp) {
				if (!Visible) Visible = true;
				if (WindowState == FormWindowState.Minimized) SafeNativeMethods.ShowWindow(Handle, SafeNativeMethods.SW_RESTORE);
				if (SafeNativeMethods.GetForegroundWindow() != Handle) SafeNativeMethods.SetForegroundWindow(Handle);
				if (SafeNativeMethods.GetForegroundWindow() != Handle && Icon != mainIcon) {
					Icon = notifyIcon.Icon = mainIcon;
				}
			} else if (Icon != mainIcon && SafeNativeMethods.GetForegroundWindow() != Handle) {
				Icon = notifyIcon.Icon = mainIcon;
			}
			pbarControl.Counter++;
		}

		void RunThread()
		{
			dirs.Clear();
			pbarControl.Text = "Check...";
			currentRoot = new Node<DirItem, FileItem>(new DirItem(""));
			MonitoringPaths.ForEach( m => dirs.Add( currentRoot.AddNode( new DirItem(m) ) ) );
			thread = new Thread( CheckChanges );
			thread.Start();
		}
		void CheckChanges()
		{
			string path;
			int lastDir = 0;
			Node<DirItem, FileItem> node;

			while(lastDir < dirs.Count) {
				node = dirs[lastDir];
				path = node.Value.Name;
				if (Directory.Exists(path)) {
					foreach(string s in Directory.GetDirectories(path).OrderBy(m => m)) {
						dirs.Add( node.AddNode( new DirItem( s ) ) );
					}
					foreach(string s in Directory.GetFiles(path).OrderBy(m => m)) {
						node.AddItem( new FileItem( s ) );
					}
				}
				lastDir++;
			}

			if (Root == null) {
				Root = currentRoot;
				Root.SetStatus(StatusElement.Existing);
			} else {
				Root.Synchronize(currentRoot);				
			}
			Invoke(InstanceViewChanges);
		}
		void ViewChanges()
		{
			dgvFiles.SaveSelected();
			dgvHistory.SaveSelected();

			dgvFiles.data.Clear();
			int lastDir = 0;
			List<Node<DirItem, FileItem>> dirs = new List<Node<DirItem, FileItem>>();
			dirs.AddRange(Root.Nodes);
			while(lastDir < dirs.Count) {
				dirs.AddRange(dirs[lastDir].Nodes);
				foreach(var item in dirs[lastDir].Items) {
					if (item.Status != StatusElement.Deleted) {
						dgvFiles.data.Add( item );
					}
					if (item._AddToLog) {
						dgvHistory.data.Insert(0, new LogItem(item) {Time=DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} );
					}
				}
				lastDir++;
			}
			dgvHistory.RowCount = dgvHistory.data.Count;
			dgvFiles.RowCount = dgvFiles.data.Count;
			if (dgvFiles.SortedColumn != null) {
				dgvFiles.SortData();
			}
			dgvFiles.Refresh();
			dgvFiles.RestoreSelected();
			dgvHistory.RestoreSelected();

			Root.RemoveDeleted();

			Text = string.Format("FM  - {0}: {1}, {2}: {3}", "files", dgvFiles.data.Count, "folders", dirs.Count - MonitoringPaths.Count);
			if (Text != notifyIcon.Text) {
				notifyIcon.Text = Text;
			}
			pbarControl.Text = "";
			pbarControl.Value = 0;
			pbarControl.Counter = 0;
			
			timer.Start();
		}

		public void SettingsFormShow()
		{
			settingsForm.checkBoxFloatUp.Checked = floatUp;
			settingsForm.TopMost = settingsForm.checkBoxAlwaysOnTop.Checked = TopMost;
			settingsForm.dgv.Rows.Clear();
			foreach(string path in MonitoringPaths) {
				settingsForm.dgv.Rows.Add(path);
			}
			if (settingsForm.ShowDialog(this) == DialogResult.OK) {
				TopMost = settingsForm.checkBoxAlwaysOnTop.Checked;
				floatUp = settingsForm.checkBoxFloatUp.Checked;
				MonitoringPaths.Clear();
				var rows = from DataGridViewRow row in settingsForm.dgv.Rows select row.Cells[0].Value.ToString();
				foreach(var row in rows) {
					MonitoringPaths.Add(row);
				}
				WatchersClear();
				SettingsSave();
				WatchersCreate();
			}
		}
		public bool SettingsLoad()
		{
			int vInt;
			bool vBool;
			string cfg_file = AppDomain.CurrentDomain.BaseDirectory + "config.xml";
			if (File.Exists(cfg_file)) {
				XElement cfgXML, cfgGroup, cfgParam;
				cfgXML = XElement.Load(cfg_file);
				cfgGroup = cfgXML.Element("Window");
				if (cfgGroup != null) {
					cfgParam = cfgGroup.Element("Top");
					if (cfgParam != null && int.TryParse(cfgParam.Value, out vInt) && vInt >= 0 && vInt < Screen.PrimaryScreen.WorkingArea.Height) Top = vInt;
					cfgParam = cfgGroup.Element("Left");
					if (cfgParam != null && int.TryParse(cfgParam.Value, out vInt) && vInt >= 0 && vInt < Screen.PrimaryScreen.WorkingArea.Width) Left = vInt;
					cfgParam = cfgGroup.Element("Width");
					if (cfgParam != null && int.TryParse(cfgParam.Value, out vInt) && vInt >= MinimumSize.Width && vInt < Screen.PrimaryScreen.WorkingArea.Width) Width = vInt;
					cfgParam = cfgGroup.Element("Height");
					if (cfgParam != null && int.TryParse(cfgParam.Value, out vInt) && vInt >= MinimumSize.Height && vInt < Screen.PrimaryScreen.WorkingArea.Height) Height = vInt;
					cfgParam = cfgGroup.Element("Maximized");
					if (cfgParam != null && bool.TryParse(cfgParam.Value, out vBool) && vBool) WindowState = FormWindowState.Maximized;
					cfgParam = cfgGroup.Element("AlwaysOnTop");
					if (cfgParam != null && bool.TryParse(cfgParam.Value, out vBool)) { TopMost = vBool; }
					cfgParam = cfgGroup.Element("FloatUp");
					if (cfgParam != null && bool.TryParse(cfgParam.Value, out vBool)) { floatUp = vBool; }
				}
				cfgParam = cfgXML.Element("GridHistory");
				if (cfgParam != null) SetWidthColumns(dgvHistory, cfgParam.Value);
				cfgParam = cfgXML.Element("GridFiles");
				if (cfgParam != null) SetWidthColumns(dgvFiles, cfgParam.Value);
				cfgGroup = cfgXML.Element("GridFilesSortedColumn");
				if (cfgGroup != null) {
					cfgParam = cfgGroup.Element("Name");
					if (cfgParam != null && dgvFiles.Columns.Contains(cfgParam.Value)) {
						dgvFiles.SortedColumn = dgvFiles.Columns[cfgParam.Value];
						cfgParam = cfgGroup.Element("Order");
						if (cfgParam != null) {
							var columns = from DataGridViewColumn column in dgvFiles.Columns where column.Index != dgvFiles.SortedColumn.Index select column;  
							foreach(var column in columns) {
								column.HeaderCell.SortGlyphDirection = SortOrder.None;
							}
							dgvFiles.SortedColumn.HeaderCell.SortGlyphDirection =
								cfgParam.Value == "Descending" ? SortOrder.Descending : SortOrder.Ascending;
						}
					}
				}
				cfgGroup = cfgXML.Element("Paths");
				if (cfgGroup != null) {
					foreach(var path in cfgGroup.Elements("Path")) {
						MonitoringPaths.Add(path.Value);
					}
				}
			} else {
				SetWidthColumns(dgvHistory, "110 585 95 60");
				SetWidthColumns(dgvFiles, "110 585 95 60 65");
				Width = 953;
			}
			return true;
		}
		public void SettingsSave()
		{
			XElement cfgXML = new XElement("Root");
			XElement window = new XElement("Window");
			window.Add(new XElement("Top", WindowState == FormWindowState.Normal ? Top : RestoreBounds.Top),
				new XElement("Left", WindowState == FormWindowState.Normal ? Left : RestoreBounds.Left),
				new XElement("Height", WindowState == FormWindowState.Normal ? Height : RestoreBounds.Height),
				new XElement("Width", WindowState == FormWindowState.Normal ? Width : RestoreBounds.Width),
				new XElement("Maximized", WindowState == FormWindowState.Maximized),
				new XElement("AlwaysOnTop", TopMost),
				new XElement("FloatUp", floatUp));
			cfgXML.Add(window);
			cfgXML.Add(new XElement("GridHistory", GetWidthColumns(dgvHistory)));
			cfgXML.Add(new XElement("GridFiles", GetWidthColumns(dgvFiles)));
			XElement sortedColumn = new XElement("GridFilesSortedColumn");
			sortedColumn.Add(new XElement("Name", dgvFiles.SortedColumn.Name ),
							 new XElement("Order", dgvFiles.SortedColumn.HeaderCell.SortGlyphDirection));
			cfgXML.Add(sortedColumn);
			XElement paths = new XElement("Paths");
			foreach(var path in MonitoringPaths) {
				paths.Add(new XElement("Path", path));
			}
			cfgXML.Add(paths);
			cfgXML.Save(AppDomain.CurrentDomain.BaseDirectory + "config.xml");
		}
		private string GetWidthColumns(DataGridView dgv)
		{
			var widths = from DataGridViewColumn column in dgv.Columns select column.Width.ToString();
			return dgv == null ? "" :  string.Join(" ", widths.ToArray() );
		}
		private void SetWidthColumns(DataGridView dgv, string str)
		{
			int n;
			string [] widths = str.Split(' ');
			if (dgv != null) {
				for(int i=0; i<widths.Length && i<dgv.ColumnCount; i++) {
					if (int.TryParse(widths[i], out n)) dgv.Columns[i].Width = n;
				}
			}
		}
	}
}
